import BaseDrawer from '@/components/base-drawer'
import BaseModal from '@/components/base-modal'
import {
  h,
  nextTick,
  reactive,
  ref,
  render,
  shallowRef,
  type AsyncComponentLoader,
  type Component,
  type ComponentPublicInstance,
  type Ref
} from 'vue'

export type ModalOption<T> = {
  key: T
  content: AsyncComponentLoader
  onLoaded?: (ctx: ComponentPublicInstance) => void
  afterSubmit?: (sub_type: number) => void
}

/**
 * 使用`useAsyncComponents`hook可以便捷的使用modal或drawer弹窗，并传递任意组件作为其内容。<br />
 * 有以下两种使用方式： <br/>
 * 一、使用BaseModal或者BaseDrawer。 <br />
 *  1)、在页面中引入BaseModal或者BaseDrawer; <br/>
 * ```vue
 *   <BaseModal 
 *     v-if="component_key" 
 *     :ref="(el) => (modal.ref = el)" 
 *     v-model:visible="modal.visible" 
 *     :comp="components[component_key]" 
       `@update:visible="handleVisibleChange"
 *   /> 
 * ```
 *
 *   or
 * ```vue
 *   <BaseDrawer 
 *     v-if="component_key" 
 *     :ref="(el) => (drawer.ref = el)" 
       v-model:visible="drawer.visible" 
 *     :comp="components[component_key]" 
 *   />
 * ```
 * 
 *  2)、引入hook, 上面`BaseModel`和`BaseDrawer`所需的参数均有此hook提供，其中`useAsyncComponents`中的泛型参数代表后续需要加载的组件key <br/>
 *   
 * ```js
 *   const { component_key, components, loadComponent , modal, drawer } = useAsyncComponents<'form' | 'detail'>()  
 * ```
 *  3)、在事件中，加载组件，设置相应弹窗的visible = true即可 <br/>
 * 
 * ```js
 *  const handleClick1 = async () => { 
 *     // 指定加载form表单页 <br/>
 *     await loadComponent('form', () => import('./views/AboutView.vue')) 
 *     modal.visible = true 
 *    
 *    // 若需调用内部组件暴露的方法，则可使用以下形式。 
 *     nextTick(() => {   
 *        modal.ref?.component_ref?.init() 
 *     })
 *   }
 * ```
 *  
 *  二、使用hook提供的`openModal`、`openDrwaer`方法。 <br/>
 * 
 * 
 *```js
 * const handleClick1 = async () => { 
 * await openDrawer({
 *   content: () => import('./views/Form.vue'),
 *   key: 'form',
 *   onLoaded(ctx) {
 *     // 调用组件内部方法。
 *     ctx?.init()
 *   },
 *   afterSubmit() {
 *     console.log('afterSubmit')
 *   }
 * })
```
}
 * 
 */
export const useAsyncComponents = <T extends string>() => {
  const modal = reactive<{
    visible: boolean
    ref: Ref<any> | null
  }>({
    visible: false,
    ref: null
  })

  const drawer = reactive<{
    visible: boolean
    ref: Ref<any> | null
  }>({
    visible: false,
    ref: null
  })

  // 当前组件key
  const component_key = ref<T>()

  const components = shallowRef<{ [key: string]: Component }>({})

  const loadComponent = async (key: T, asyncComponentLoader: AsyncComponentLoader) => {
    if (!components.value[key]) {
      const { default: Component } = await asyncComponentLoader()
      components.value[key] = Component
    }
    component_key.value = key
  }

  const open = async (options: ModalOption<T>, type: 'model' | 'drawer' = 'model') => {
    const key = options.key
    component_key.value = key

    if (!components.value[key]) {
      const { default: Component } = await options.content()
      components.value[key] = Component
    }

    let container: HTMLElement | null = document.createElement('div')

    const vnode = h(type == 'model' ? BaseModal : BaseDrawer, {
      visible: true,
      comp: components.value[key],
      'onUpdate:visible': (bool: boolean) => {
        if (!bool) {
          if (container) {
            render(null, container)
          }
          container = null
        }
      },
      onAfterSubmit: (sub_type: number) => {
        options.afterSubmit?.(sub_type)
      }
    })

    render(vnode, container)

    // VNode渲染完毕之后，传递内部的组件实例到onload事件中，便于处理其他额外逻辑，例如内部组件初始化等等。
    nextTick(() => {
      options.onLoaded?.(vnode.component?.refs.component_ref as ComponentPublicInstance)
    })
  }

  const openModal = async (options: ModalOption<T>) => {
    open(options, 'model')
  }

  const openDrawer = async (options: ModalOption<T>) => {
    open(options, 'drawer')
  }

  return {
    modal,
    drawer,
    component_key,
    components,
    loadComponent,
    openModal,
    openDrawer
  }
}
